// 1. 上级作用域：函数在哪里创建的，上级作用域就是谁

// eg1.
var a = 1;
function bar() {
    console.log(a);
};

function foo() {
    var a = 2;
    bar();
};

foo(); // 1  [bar是在全局作用域下创建的]

// eg2.
var n = 10
function fn(){
    var n =20
    function f() {
       n++;
       console.log(n)
     };
    f();   // f在这里执行了： 打印21
    return f;
};

var x = fn()  // 把f函数赋给x
x();   // 调用x 打印22
x();   // 调用x 打印23
console.log(n);   // 打印10


// 2. 闭包：有权访问另一个函数作用域中的变量
// 形成原因：内部的函数存在外部作用域的引用就会导致闭包
// 闭包的作用: (1)保护函数的私有变量不受外部的干扰。形成不销毁的栈内存; (2)保存，把一些函数内的值保存下来。闭包可以实现方法和属性的私有化

// 3. 应用场景
// eg1. return回一个函数
var n = 10;
function fn() {
    var n = 20;
    function f() {
        n++;
        console.log(n)
    };

    return f;
};

var x = fn();
x();  // 21


// eg2. 函数作为参数
var b = 'Katrina';
function foo() {
    var b = 'hi';
    function fo() {
        console.log(b);
    };

    return fo;
};

function f(p) {
    var b = 'f';
    p();
};

f(foo());     // hi


// eg3. IIFE（自执行函数）
var c = 'Katrina';
(function p(){
    console.log(c)
})()   // Katrina


// eg4. 循环赋值
for (var i = 0; i < 10; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j)
        }, 1000)
    })(i);
};


// eg5. 使用回调函数就是在使用闭包
window.name = 'Katrina'
setTimeout(function timeHandler(){
  console.log(window.name);
}, 100);


// eg6. 防抖和节流
function debounce(fn, delay) {
    let timer;

    return function() {
        if (timer) {
            clearTimeout(timer);
        };

        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, delay)
    };
};


function throttle(fn, delay) {
    let timer = null;

    return function() {
        if (timer) {
            return;
        } 
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
        }, delay);
    }
};

// eg7. 柯里化实现
function curry(fn, len = fn.length) {
    return _curry(fn, len)
}

function _curry(fn, len, ...arg) {
    return function (...params) {
        let _arg = [...arg, ...params]
        if (_arg.length >= len) {
            return fn.apply(this, _arg)
        } else {
            return _curry.call(this, fn, len, ..._arg)
        }
    }
}

//----------------------------------------------------------------------------------------------------------------------------------------

// 经典面试题： for 循环和闭包
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]()


// 写法1：自执行函数和闭包
var data = [];

for (var i = 0; i < 3; i++) {
    (function(j){
        setTimeout( data[j] = function () {
          console.log(j);
        }, 0)
      })(i)
};


// 写法2： 使用let
var data = [];

for (let i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
};


// 拓展：找不同（这里也形成了闭包 total 被外层引用没有被销毁）
var result = [];
var a = 3;
var total = 0;

function foo(a) {
    for (var i = 0; i < 3; i++) {
        result[i] = function () {
            total += i * a;
            console.log(total);
        }
    }
};


